//==============================================================================
//
//  This file is part of GPSTk, the GPS Toolkit.
//
//  The GPSTk is free software; you can redistribute it and/or modify
//  it under the terms of the GNU Lesser General Public License as published
//  by the Free Software Foundation; either version 3.0 of the License, or
//  any later version.
//
//  The GPSTk is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU Lesser General Public License for more details.
//
//  You should have received a copy of the GNU Lesser General Public
//  License along with GPSTk; if not, write to the Free Software Foundation,
//  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA
//  
//  This software was developed by Applied Research Laboratories at the
//  University of Texas at Austin.
//  Copyright 2004-2020, The Board of Regents of The University of Texas System
//
//==============================================================================

//==============================================================================
//
//  This software was developed by Applied Research Laboratories at the
//  University of Texas at Austin, under contract to an agency or agencies
//  within the U.S. Department of Defense. The U.S. Government retains all
//  rights to use, duplicate, distribute, disclose, or release this software.
//
//  Pursuant to DoD Directive 523024 
//
//  DISTRIBUTION STATEMENT A: This software has been approved for public 
//                            release, distribution is unlimited.
//
//==============================================================================

/** @file Rinex3EphemerisStore.hpp Read and store RINEX formated
 * navigation message (Rinex3Nav) data, following the RINEX 3.02
 * spec. Support for GNSS GPS, GAL, GLO, BDS, QZS (GEO TBD). */

#ifndef GPSTK_RINEX3EPHEMERISSTORE_HPP
#define GPSTK_RINEX3EPHEMERISSTORE_HPP

#include <iostream>
#include <string>
#include <list>
#include <map>
#include <set>
#include <algorithm>

#include "Exception.hpp"
#include "CommonTime.hpp"
#include "BDSWeekSecond.hpp"
#include "GPSWeekSecond.hpp"
#include "SatID.hpp"
#include "Xvt.hpp"
#include "Rinex3NavHeader.hpp"
#include "Rinex3NavData.hpp"
#include "TimeSystemCorr.hpp"

#include "FileStore.hpp"

#include "OrbitEphStore.hpp"
#include "GloEphemerisStore.hpp"
//#include "GeoEphemerisStore.hpp"

namespace gpstk
{
      /// @ingroup GNSSEph
      //@{

   class Rinex3EphemerisStore : public XvtStore<SatID>
   {
         // member data:
   private:

         /// FileStore for the Rinex3Nav input files
      FileStore<Rinex3NavHeader> NavFiles;

         /** Ephemeris store for orbit-based nav messages (stored as
          * descendents of OrbitEph), including systems GPS, Galileo,
          * BeiDou, and QZSS */
      OrbitEphStore ORBstore;

         /// Ephemeris store for Glonass nav messages (stored as GloRecord)
      GloEphemerisStore GLOstore;

         /// Ephemeris store for Geosync nav messages (stored as GeoRecord)
         //GeoEphemerisStore GEOstore;

   public:

         /// Rinex file header last read by loadFile()
      Rinex3NavHeader Rhead;

         /// Rinex file data last read by loadFile()
      Rinex3NavData Rdata;

         /** Map of time system corrections, similar to mapTimeCorr in
          * Rinex3NavHeader, and taken from either loadFile()
          * (RinexNavHeader) or user input.  key =
          * TimeSystemCorrection::asString4().  User may add to the
          * list with addTimeCorr() */
      std::map<std::string, TimeSystemCorrection> mapTimeCorr;

         /// string containing what() of exceptions caught by loadFile()
      std::string what;

         // member functions:

      Rinex3EphemerisStore()
      {
         bool flag = ORBstore.getOnlyHealthyFlag(); // default is ORB default
         GLOstore.setCheckHealthFlag(flag);
      }

         /// destructor
      virtual ~Rinex3EphemerisStore()
      { }
      
         // XvtStore interface:

         /** Returns the position, velocity, and clock offset of the indicated
          * object in ECEF coordinates (meters) at the indicated time.
          * @param[in] sat the satellite of interest
          * @param[in] ttag the time to look up
          * @return the Xvt of the object at the indicated time
          * @throw InvalidRequest If the request can not be completed for any
          *    reason, this is thrown. The text may have additional
          *    information as to why the request failed. */
      virtual Xvt getXvt(const SatID& sat, const CommonTime& ttag) const;

         /** Compute the position, velocity and clock offset of the
          * indicated object in ECEF coordinates (meters) at the
          * indicated time.
          * This method functions similarly to getXvt() except that it
          * does not throw an exception for any reason.  Instead, the
          * caller is expected to check the value of the "health"
          * field of the returned Xvt and decide what to do with the
          * data.
          * @note This function ignores the onlyHealthy flag.  It is
          *   up to the caller to examine the state of the health flag
          *   and decide what to do.
          * @param[in] id the object's identifier
          * @param[in] t the time to look up
          * @return the Xvt of the object at the indicated time */
      virtual Xvt computeXvt(const SatID& id, const CommonTime& t) const
         throw();

         /** Get the satellite health at a specific time.
          * @param[in] id the object's identifier
          * @param[in] t the time to look up
          * @return the health status of the object at the indicated time. */
      virtual Xvt::HealthStatus getSVHealth(const SatID& id,
                                            const CommonTime& t) const throw();

         /** Dump information about the store to an ostream.
          * @param[in] os ostream to receive the output; defaults to std::cout
          * @param[in] detail integer level of detail to provide;
          *   allowed values are
          *    0: number of satellites, time step and time limits (default)
          *    1: above plus flags, gap and interval values, and
          *       number of data/sat
          *    2: above plus all the data tables */
      virtual void dump(std::ostream& os=std::cout, short detail=0) const;

         /** Edit the dataset, removing data outside the indicated
          * time interval
          * @param[in] tmin defines the beginning of the time interval
          * @param[in] tmax defines the end of the time interval */
      virtual void edit(const CommonTime& tmin, 
                        const CommonTime& tmax = CommonTime::END_OF_TIME)
      {
         if(ORBstore.size()) ORBstore.edit(tmin, tmax);
         if(GLOstore.size()) GLOstore.edit(tmin, tmax);
            //if(GEOstore.size()) GEOstore.edit(tmin, tmax);
      }

         /// Clear the dataset, meaning remove all data
      virtual void clear(void)
      {
         NavFiles.clear();
         ORBstore.clear();
         GLOstore.clear();
            //GEOstore.clear();
      }

         /** Return time system of this store. 
          * @note This is needed only to satisfy the XvtStore virtual
          * interface; the system stores (GPSstore, GLOstore, etc)
          * will be used internally to determine time system. */
      virtual TimeSystem getTimeSystem(void) const
      { return TimeSystem::Any; }

         /** Determine the earliest time for which this object can
          * successfully determine the Xvt for any object.
          * @return the earliest time in the table
          * @throw InvalidRequest if the object has no data. */
      virtual CommonTime getInitialTime(void) const;

         /** Determine the latest time for which this object can successfully 
          * determine the Xvt for any object.
          * @return the latest time in the table
          * @throw InvalidRequest if the object has no data. */
      virtual CommonTime getFinalTime(void) const;

         /// Return true if IndexType=SatID is present in the data tables
      virtual bool isPresent(const SatID& sat) const
      {
         switch(sat.system) {
            case SatelliteSystem::GPS:
            case SatelliteSystem::Galileo:
            case SatelliteSystem::BeiDou:
            case SatelliteSystem::QZSS:
               return ORBstore.isPresent(sat);
            case SatelliteSystem::Glonass:
               return GLOstore.isPresent(sat);
                  //case SatelliteSystem::Geosync:
                  //return GEOstore.isPresent(sat);
            default:
               return false;
         }
         return false;
      }

         /// Return true if velocity is present in the data tables
      virtual bool hasVelocity(void) const
      { return true; }

         // end of XvtStore interface

         /** Determine the earliest time for which this object can
          * successfully determine the Xvt for this satellite or system
          * (sat.id == -1).
          * @param SatID sat satellite, or system if sat.id==-1
          * @return the earliest time in the table
          * @throw InvalidRequest if the object has no data. */
      virtual CommonTime getInitialTime(const SatID& sat) const;

         /** Determine the latest time for which this object can successfully 
          * determine the Xvt for this satellite or system (sat.id == -1).
          * @param SatID sat satellite, or system if sat.id==-1
          * @return the latest time in the table
          * @throw InvalidRequest if the object has no data. */
      virtual CommonTime getFinalTime(const SatID& sat) const;

         /** add a Rinex3NavData to the store
          * @param Rinex3NavData Rdata data to be added
          * @return true if data was added, false otherwise */
      bool addEphemeris(const Rinex3NavData& Rdata);

         /** add filename and header to FileStore
          * @param string filename file name to be added
          * @param Rinex3NavHeader head header to be added */
      void addFile(const std::string& filename, Rinex3NavHeader& head)
      { NavFiles.addFile(filename,head); }

         /** load a RINEX navigation file
          * @param string filename name of the RINEX navigation file to read
          * @param bool dump if true, dump header and nav data as
          *   read, default false
          * @param ostream stream to which dump is written, default cout
          * @return -1 failed to open file,
          *         -2 failed to read header (this->Rhead),
          *         -3 failed to read data (this->Rdata),
          *        >=0 number of nav records read
          * @throw some other problem */
      int loadFile(const std::string& filename, bool dump=false,
                   std::ostream& s=std::cout);

         /** use to access the data records in the store in bulk Add
          * all Rinex3NavData in this store to the given list. If sat
          * is defined, (its default is (-1,mixed)), then add only
          * objects of sat's system, further if sat.id is not -1, add
          * only records for that sat. */
      int addToList(std::list<Rinex3NavData>& theList,
                    SatID sat=SatID(-1,SatelliteSystem::Mixed)) const;

         /** get the number of records or ephemerides for the given
          * satellite; an overload size(SatelliteSystem) gives
          * the total per system.
          * @param SatID sat satellite
          * @return the number of records or ephemerides */
      int size(const SatID sat) const
      {
         int n(0);
         SatelliteSystem sys = sat.system;

         if(sys == SatelliteSystem::Mixed || sys == SatelliteSystem::GPS ||
            sys == SatelliteSystem::Galileo || sys == SatelliteSystem::BeiDou ||
            sys == SatelliteSystem::QZSS)
            n += ORBstore.size(sat);

         if(sys == SatelliteSystem::Mixed || sys == SatelliteSystem::Glonass)
            n += GLOstore.size();

            //if(sys == SatelliteSystem::Mixed || sys == SatelliteSystem::Geosync)
            //   n += GEOstore.size();

         return n;
      }

         /** get the number of records by system; default is systemMixed,
          * which returns the overall total
          * @param SatelliteSystem GNSS of interest
          * @return the number of records or ephemerides in this system */
      int size(const SatelliteSystem sys = SatelliteSystem::Mixed) const
      { return size(SatID(-1,sys)); }

         /** Add to the map of time system corrections. Overwrite the existing
          * correction of the same type, if it exists.
          * @return true if an existing correction was overwritten. */
      bool addTimeCorr(const TimeSystemCorrection& tsc)
      {
            // true if this type already exists
         bool overwrite(mapTimeCorr.find(tsc.asString4()) != mapTimeCorr.end());

            // add or overwrite it
         mapTimeCorr[tsc.asString4()] = tsc;

         return overwrite;
      }

         /** Delete from the map of time system corrections.
          * @param type of TimeSystemCorrection, as a string,
          *                   i.e. TimeSystemCorrection::asString4()
          * @return true if an existing correction was deleted. */
      bool delTimeCorr(const std::string& typestr)
      {
         std::map<std::string, TimeSystemCorrection>::iterator it;
         it = mapTimeCorr.find(typestr);
         if(it != mapTimeCorr.end()) {
            mapTimeCorr.erase(it);
            return true;
         }
         return false;
      }

         /** Fill out the time system corrections "network" by adding
          * corrections that can be derived from existing
          * corrections. For example,
          *   given GPUT (GPS to UTC(USNO), from LEAP SECONDS)
          *     and GLUT (GLO to UTC(SU), from CORR TO SYSTEM TIME)
          * compute GLGP (GLO to GPS) by assuming all UTC's are equivalent.
          * @return the number of new TimeSystemCorrection's */
      int expandTimeCorrMap(void)
      {
         int n(0);
         std::map<std::string, TimeSystemCorrection>::iterator it,jt;

            // GLGP : GLO to GPS
         if(mapTimeCorr.find(std::string("GLGP")) == mapTimeCorr.end()) {
            it = mapTimeCorr.find(std::string("GPUT"));
            jt = mapTimeCorr.find(std::string("GLUT"));
            if(it != mapTimeCorr.end() && jt != mapTimeCorr.end()
               && mapTimeCorr.find(std::string("GLGP")) == mapTimeCorr.end())
            {
               TimeSystemCorrection tc("GLGP");
               tc.A0 = jt->second.A0 - it->second.A0;
               tc.A1 = jt->second.A1 - it->second.A1; // probably zeros
               tc.refYr = it->second.refYr;
               tc.refMon = it->second.refMon;
               tc.refDay = it->second.refDay;
               tc.refWeek = jt->second.refWeek;
               tc.refSOW = jt->second.refSOW;
               tc.geoProvider = jt->second.geoProvider;  // blank
               tc.geoUTCid = 0;                          // NA
               mapTimeCorr[tc.asString4()] = tc;
               n++;
            }
         }

            // GPGA : GPS to GAL
         if(mapTimeCorr.find(std::string("GPGA")) == mapTimeCorr.end()) {
            it = mapTimeCorr.find(std::string("GAUT"));
            jt = mapTimeCorr.find(std::string("GPUT"));
            if(it != mapTimeCorr.end() && jt != mapTimeCorr.end()
               && mapTimeCorr.find(std::string("GPGA")) == mapTimeCorr.end())
            {
               TimeSystemCorrection tc("GPGA");
               tc.A0 = jt->second.A0 - it->second.A0;
               tc.A1 = jt->second.A1 - it->second.A1;
               tc.refYr = it->second.refYr;           // take ref time from GAL
               tc.refMon = it->second.refMon;
               tc.refDay = it->second.refDay;
               tc.refWeek = it->second.refWeek;
               tc.refSOW = it->second.refSOW;
               tc.geoProvider = it->second.geoProvider;  // blank
               tc.geoUTCid = 0;                          // NA
               mapTimeCorr[tc.asString4()] = tc;
               n++;
            }
         }

            // BDGP : BDS to GPS
         if(mapTimeCorr.find(std::string("BDGP")) == mapTimeCorr.end()) {
            it = mapTimeCorr.find(std::string("GPUT"));
            jt = mapTimeCorr.find(std::string("BDUT"));
            if(it != mapTimeCorr.end() && jt != mapTimeCorr.end()
               && mapTimeCorr.find(std::string("BDGP")) == mapTimeCorr.end())
            {
               TimeSystemCorrection tc("BDGP");
               tc.A0 = jt->second.A0 - it->second.A0;
               tc.A1 = jt->second.A1 - it->second.A1;
               tc.refYr = it->second.refYr;
               tc.refMon = it->second.refMon;
               tc.refDay = it->second.refDay;
               BDSWeekSecond bws(jt->second.refWeek,jt->second.refSOW);
               GPSWeekSecond gws(bws);
               tc.refWeek = gws.week;
               tc.refSOW = gws.sow;
               tc.geoProvider = jt->second.geoProvider;  // blank
               tc.geoUTCid = 7;
               mapTimeCorr[tc.asString4()] = tc;
               n++;
            }
         }

         return n;
      }

         /** Utility routine for getXvt and addEphemeris to test time
          * systems and convert if necessary. Convert ttag to the
          * target time system, using the first appropriate correction
          * in mapTimeCorr, and return it.  If no correction is found,
          * ttag is unchanged and an exception is thrown. */
      CommonTime correctTimeSystem(const CommonTime ttag,
                                   const TimeSystem targetSys) const;

         /** Find the appropriate time system correction object in the
          * collection for the given time systems, and dump it to a
          * string and return that string. */
      std::string dumpTimeSystemCorrection(const TimeSystem fromSys,
                                           const TimeSystem toSys) const;

         /// Get integration step for GLONASS Runge-Kutta algorithm (seconds)
      double getGLOStep(void) const
      { return GLOstore.getIntegrationStep(); }

         /// Set integration step for GLONASS Runge-Kutta algorithm (seconds)
      void setGLOStep(double step)
      { GLOstore.setIntegrationStep(step); }

         /** Get flag that causes unhealthy (Orbit-based) ephemerides
          * to be excluded */
      bool getOnlyHealthyFlag(void) const
      {
         return ORBstore.getOnlyHealthyFlag();
      }

         /// Set flag that causes unhealthy ephemerides to be excluded
      void setOnlyHealthyFlag(bool flag)
      {
         ORBstore.setOnlyHealthyFlag(flag);
         GLOstore.setCheckHealthFlag(flag);
      }

         /** use findNearEphemeris() in the getSat...() routines
          * (Orbit-based systems) */
      void SearchNear(void)
      {
         ORBstore.SearchNear();
      }

         /** use findEphemeris() in the getSat...() routines (the
          * default) (Orbits) */
      void SearchUser(void)
      {
         ORBstore.SearchUser();
      }

      virtual std::set<gpstk::SatID> getIndexSet() const
      {
         std::set<gpstk::SatID> retSet;
         retSet = ORBstore.getIndexSet();
         std::set<gpstk::SatID> set2;
         set2 = GLOstore.getIndexSet();
         std::set<gpstk::SatID>::const_iterator cit;
         for (cit=set2.begin();cit!=set2.end();cit++)
         {
            const gpstk::SatID sidr = *cit;
            retSet.insert(sidr);
         }
         return retSet;
      }

   }; // end class Rinex3EphemerisStore

      //@}

}  // namespace

#endif // GPSTK_RINEX3EPHEMERISSTORE_HPP
